//
#include <g-bios.h>
#include <flash/flash.h>
#include <flash/nand.h>
#include <arch/s3c24xx.h>
#include <flash/nand.h>


static struct NandOobLayout gS3cEccLayout = {
	.nEccCodeNum = 3,
	.piEccPos    = {0, 1, 2},
	.mFreeRegion = {{8, 8}}
};

static void S3C2410NandEnable(struct NandCtrl *pNandCtrl)
{
	UINT16 usStat;

	usStat = readl((void *)(NAND_CTRL_BASE + NF_CONF));

	usStat |= 1 << 15;

	writel(usStat, (void *)(NAND_CTRL_BASE + NF_CONF));
}

static void S3C2410NandDisable(struct NandCtrl *pNandCtrl)
{
	UINT16 usStat;
	
	usStat = readl((void *)(NAND_CTRL_BASE + NF_CONF));
	usStat &= ~(1 << 15);
	writel(usStat, (void *)(NAND_CTRL_BASE + NF_CONF));
}


static int S3C2410NandInit(struct NandCtrl *pNandCtrl)
{
	UINT16 usCmd = ((1 << 12) | (3 << 8) | (3 << 4) | 3);

	writel(usCmd, (void *)(NAND_CTRL_BASE + NF_CONF));

	return 0;
}

static void S3C2410NandCmdCtrl(struct NandFlash *pNandFlash, int cmd, UINT32 ulCtrl)
{
	if (cmd == NAND_CMMD_NONE)
		return;

	if (ulCtrl & NAND_CLE)
		writeb(cmd, (void *)(NAND_CTRL_BASE + NF_CMMD));
	else
		writeb(cmd, (void *)(NAND_CTRL_BASE + NF_ADDR));
}

static void S3C2410NandEnableHWEcc(struct NandFlash *pNandFlash, int mode)
{
	UINT16 usCtl;

	usCtl = readw((void *)(NAND_CTRL_BASE + NF_CONF));
	usCtl |= (1 << 12);
	writew(usCtl, (void *)(NAND_CTRL_BASE + NF_CONF));
}


static int S3C2410NandCalculateHWEcc(struct NandFlash *pNandFlash, const UINT8 *pbData, UINT8 *pbEccCode)
{
	pbEccCode[0] = readb((void *)(NAND_CTRL_BASE + NF_ECC + 0));
	pbEccCode[1] = readb((void *)(NAND_CTRL_BASE + NF_ECC + 1));
	pbEccCode[2] = readb((void *)(NAND_CTRL_BASE + NF_ECC + 2));

	return 0;
}


static int S3C2410NandCorrectData(struct NandFlash *pNandFlash,
										UINT8 *pbData,
										UINT8 *pbReadEcc,
										UINT8 *pbCalcEcc
										)
{
	UINT32 diff0, diff1, diff2;
	UINT32 bit, byte;


	diff0 = pbReadEcc[0] ^ pbCalcEcc[0];
	diff1 = pbReadEcc[1] ^ pbCalcEcc[1];
	diff2 = pbReadEcc[2] ^ pbCalcEcc[2];

	if (diff0 == 0 && diff1 == 0 && diff2 == 0)
	{
		return 0;
	}

	// TODO:  add data correction code here

	return -1;
}


static int S3C2410NandReady(struct NandFlash *pNandFlash)
{
	return readb((void *)(NAND_CTRL_BASE + NF_STAT)) & 0x01;
}


static __INIT__ int S3C2410NandProbe(void)
{
	int ret;
	struct NandCtrl *pNandCtrl;
	ECC_MODE eOldMode; 


	printf("%s():\n", __FUNCTION__);

	pNandCtrl = GkNandCtrlNew();

	if (NULL == pNandCtrl)
	{
		return -ENOMEM;
	}

	pNandCtrl->pReadDataAddr  = (void *)(NAND_CTRL_BASE + NF_DATA);
	pNandCtrl->pWriteDataAddr = (void *)(NAND_CTRL_BASE + NF_DATA);

	strcpy(pNandCtrl->szNcName, "s3c_nand0");

	pNandCtrl->pHardOobLayout = &gS3cEccLayout;

	pNandCtrl->CmdCtrl  	  = S3C2410NandCmdCtrl;
	pNandCtrl->FlashIsReady   = S3C2410NandReady;
	pNandCtrl->EccEnable      = S3C2410NandEnableHWEcc;
	pNandCtrl->EccGenerate    = S3C2410NandCalculateHWEcc;
	pNandCtrl->EccCorrect     = S3C2410NandCorrectData;
	pNandCtrl->nEccDataLen    = 512;
	pNandCtrl->nEccCodeNum    = 3;

	GkNandSetEccMode(pNandCtrl, CONF_NAND_ECC_MODE, &eOldMode);

	S3C2410NandInit(pNandCtrl);

	S3C2410NandEnable(pNandCtrl);

	ret = GkNandCtrlRegister(pNandCtrl);
	if (ret < 0) 
	{
		ret = -ENODEV;
		goto L1;
	}

	return 0;

L1:
	S3C2410NandDisable(pNandCtrl);

	kfree(pNandCtrl);

	return ret;
}

DRIVER_INIT(S3C2410NandProbe);

